package com.ruoyi.hospital.service.impl;

import com.ruoyi.hospital.domain.Hospital;
import com.ruoyi.hospital.mapper.HospitalMapper;
import com.ruoyi.hospital.service.IHospitalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class HospitalServiceImpl implements IHospitalService {

    private static final String GEO_KEY = "hospitals:geo";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private HospitalMapper hospitalMapper;

    @Override
    public Hospital selectHospitalById(Long id) {
        return hospitalMapper.selectHospitalById(id);
    }

    @Override
    public List<Hospital> selectHospitalByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyList();
        }
        return hospitalMapper.selectHospitalByIds(ids);
    }

    @Override
    public List<Hospital> selectHospitalList(Hospital hospital) {
        return hospitalMapper.selectHospitalList(hospital);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertHospital(Hospital hospital) {
        int rows = hospitalMapper.insertHospital(hospital);
        if (rows > 0 && isValidGeoHospital(hospital)) {
            redisTemplate.opsForGeo().add(GEO_KEY,
                    new Point(hospital.getLongitude(), hospital.getLatitude()),
                    hospital.getId().toString());
        }
        return rows;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateHospital(Hospital hospital) {
        int rows = hospitalMapper.updateHospital(hospital);
        if (rows > 0 && isValidGeoHospital(hospital)) {
            redisTemplate.opsForGeo().add(GEO_KEY,
                    new Point(hospital.getLongitude(), hospital.getLatitude()),
                    hospital.getId().toString());
        }
        return rows;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteHospitalById(Long id) {
        redisTemplate.opsForZSet().remove(GEO_KEY, id.toString());
        return hospitalMapper.deleteHospitalById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteHospitalByIds(Long[] ids) {
        if (ids != null && ids.length > 0) {
            redisTemplate.opsForZSet().remove(GEO_KEY,
                    Arrays.stream(ids).map(String::valueOf).toArray());
        }
        return hospitalMapper.deleteHospitalByIds(ids);
    }

    @Override
    public List<Hospital> selectNearbyHospitals(double longitude, double latitude, double radius, int pageNum, int pageSize) {
        Circle circle = new Circle(new Point(longitude, latitude),
                new Distance(radius, Metrics.KILOMETERS));

        GeoResults<RedisGeoCommands.GeoLocation<String>> results =
                redisTemplate.opsForGeo().radius(GEO_KEY, circle,
                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                                .includeDistance().sortAscending());

        if (results == null || results.getContent().isEmpty()) {
            return Collections.emptyList();
        }

        List<RedisGeoCommands.GeoLocation<String>> geoList = results.getContent().stream()
                .map(GeoResult::getContent)
                .collect(Collectors.toList());

        // Redis 分页：计算 fromIndex / toIndex
        int fromIndex = Math.min((pageNum - 1) * pageSize, geoList.size());
        int toIndex = Math.min(fromIndex + pageSize, geoList.size());
        List<RedisGeoCommands.GeoLocation<String>> pageGeoList = geoList.subList(fromIndex, toIndex);

        List<Long> ids = pageGeoList.stream()
                .map(g -> Long.valueOf(g.getName()))
                .collect(Collectors.toList());

        Map<Long, Hospital> hospitalMap = hospitalMapper.selectHospitalByIds(ids).stream()
                .collect(Collectors.toMap(Hospital::getId, h -> h));

        return pageGeoList.stream()
                .map(g -> {
                    Long id = Long.valueOf(g.getName());
                    Hospital hospital = hospitalMap.get(id);
                    if (hospital != null) {
                        // 设置距离
                        GeoResult<RedisGeoCommands.GeoLocation<String>> r = results.getContent().stream()
                                .filter(rr -> rr.getContent().getName().equals(g.getName()))
                                .findFirst().orElse(null);
                        if (r != null) {
                            hospital.setDistance(r.getDistance().getValue());
                        }
                    }
                    return hospital;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int batchUpdateCoordinates(List<Hospital> hospitals) {
        if (hospitals == null || hospitals.isEmpty()) {
            return 0;
        }
        int updated = hospitalMapper.batchUpdateCoordinates(hospitals);

        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Hospital hospital : hospitals) {
                if (isValidGeoHospital(hospital)) {
                    redisTemplate.opsForGeo().add(GEO_KEY,
                            new Point(hospital.getLongitude(), hospital.getLatitude()),
                            hospital.getId().toString());
                }
            }
            return null;
        });
        return updated;
    }

    private boolean isValidGeoHospital(Hospital hospital) {
        return hospital != null &&
                hospital.getLongitude() != null &&
                hospital.getLatitude() != null &&
                Math.abs(hospital.getLongitude()) <= 180 &&
                Math.abs(hospital.getLatitude()) <= 90;
    }
}
